package command

import (
	"context"
	"fmt"
	"io/fs"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/fsnotify/fsnotify"
)

type ServerCommand struct{}

func (i *ServerCommand) Desc() *CommandDesc {
	return &CommandDesc{
		Name: "server",
		Methods: []*CommandMethod{
			{
				Name: "start",
				Desc: "启动应用，必须在项目所在目录执行",
				Params: []*CommandParams{
					{Name: "projectPath", Desc: "项目根目录，默认当前目录", Optional: true},
				},
			},
			{
				Name: "watch",
				Desc: "启动应用并监听文件变化，必须在项目所在目录执行",
				Params: []*CommandParams{
					{Name: "projectPath", Desc: "项目根目录，默认当前目录", Optional: true},
					{Name: "interval", Desc: "监听间隔(秒)，默认5秒", Optional: true},
				},
			},
		},
	}
}

func (i *ServerCommand) Start(projectPath string) {
	if projectPath == "" {
		projectPath = GetCurrentDir()
	}

	log.Println("启动程序 ...")
	cmd := exec.Command("go", "run", "server/main.go")
	cmd.Dir = projectPath
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stdout
	err := cmd.Run()
	HandleError(err)
}

func (i *ServerCommand) Watch(projectPath, interval string) {
	if projectPath == "" {
		projectPath = GetCurrentDir()
	}
	if interval == "" {
		interval = "5"
	}

	second, _ := strconv.ParseInt(interval, 10, 64)

	var changeFiles = []string{}
	var run = make(chan int64, 1)
	var outputFile = make(chan string, 1)
	var runnable bool
	var cf context.CancelFunc
	programName := "watcher"
	if GetGOENV("GOOS") == "windows" {
		programName = programName + ".exe"
	}

	var includePathes = []string{}
	var watchConfFileContent []byte
	var exists bool
	var err error
	watchConfFile := filepath.Clean(fmt.Sprintf("%s/%s", projectPath, ".watch"))
	exists, err = PathExists(watchConfFile)
	if exists && err == nil {
		watchConfFileContent, err = ioutil.ReadFile(watchConfFile)
	} else {
		log.Println(" .watch 文件不存在，使用默认配置进行监听")
	}
	if err != nil || exists == false {
		includePathes = []string{
			filepath.Clean(fmt.Sprintf("%s/%s", projectPath, "app")),
			filepath.Clean(fmt.Sprintf("%s/%s", projectPath, "autoload")),
			filepath.Clean(fmt.Sprintf("%s/%s", projectPath, "base")),
			filepath.Clean(fmt.Sprintf("%s/%s", projectPath, "config")),
			filepath.Clean(fmt.Sprintf("%s/%s", projectPath, "docker")),
			filepath.Clean(fmt.Sprintf("%s/%s", projectPath, "global")),
			filepath.Clean(fmt.Sprintf("%s/%s", projectPath, "server")),
			filepath.Clean(fmt.Sprintf("%s/%s", projectPath, "proto")),
			filepath.Clean(fmt.Sprintf("%s/%s", projectPath, "go.mod")),
			filepath.Clean(fmt.Sprintf("%s/%s", projectPath, "go.sum")),
			filepath.Clean(fmt.Sprintf("%s/%s", projectPath, "Makefile")),
			filepath.Clean(fmt.Sprintf("%s/%s", projectPath, "README.md")),
		}
	} else {
		watchContent := strings.ReplaceAll(string(watchConfFileContent), "\r\n", "\n")
		includeFiles := strings.Split(watchContent, "\n")
		for _, f := range includeFiles {
			fName := strings.Trim(f, " ")
			if fName == "" {
				continue
			}
			includePathes = append(includePathes, filepath.Clean(fmt.Sprintf("%s/%s", projectPath, fName)))
		}
	}

	// 添加监听文件夹
	watcher, err := fsnotify.NewWatcher()
	HandleError(err)
	defer watcher.Close()
	log.Println("监听文件 ...")
	err = filepath.Walk(projectPath, func(fPath string, info fs.FileInfo, err error) error {
		ignore := true
		for _, includePath := range includePathes {
			if isdir, _ := IsDir(includePath); isdir {
				includePath = includePath + string(os.PathSeparator)
			}
			if strings.HasPrefix(fPath, includePath) {
				ignore = false
				break
			}
		}
		if !ignore {
			err = watcher.Add(fPath)
		}
		return err
	})
	HandleError(err)

	// 启动
	go func() {
		defer Recover()
		for {
			select {
			case <-run:
				if !runnable {
					runnable = true
					go func() {
						defer Recover()
						if len(changeFiles) > 0 {
							fmt.Println("\n")
						}
						time.Sleep(time.Duration(second) * time.Second)
						changeFiles = []string{}
						if cf != nil {
							log.Println("终止程序 ... ")
							cf()
						}

						log.Printf("构建程序 ...\n")
						cmd := exec.Command("go", "build", "-o", filepath.Clean(fmt.Sprintf("%s/bin/%s", projectPath, programName)), "server/main.go")
						cmd.Dir = projectPath
						cmd.Stdout = os.Stdout
						cmd.Stderr = os.Stdout
						err = cmd.Run()
						if err != nil {
							log.Printf("构建失败: %v", err)
						}

						log.Println("启动程序 ...")
						bgCtx := context.Background()
						ctx, cancelFunc := context.WithCancel(bgCtx)
						cf = cancelFunc
						cmd = exec.CommandContext(ctx, filepath.Clean(fmt.Sprintf("%s/bin/%s", projectPath, programName)))
						cmd.Stdout = os.Stdout
						cmd.Stderr = os.Stdout
						err = cmd.Start()
						HandleError(err)

						runnable = false
					}()
				}
			}
		}
	}()

	// 输出变化的文件
	go func() {
		defer Recover()
		for {
			select {
			case file := <-outputFile:
				log.Printf("监测到文件变化 : [ %s ]", file)
			}
		}
	}()

	// 文件监听
	go func() {
		defer Recover()
		for {
			select {
			case event := <-watcher.Events:
				insert := true
				filename := filepath.Clean(strings.Trim(event.Name, "~"))
				for _, item := range changeFiles {
					if item == filename {
						insert = false
					}
				}
				if insert {
					changeFiles = append(changeFiles, filename)
					outputFile <- filename
				}
				run <- second
			}
		}
	}()

	// 首次启动
	run <- 0

	<-make(chan struct{})
}
